#include <jni.h>
#include <string>
#include <stdlib.h>

#include <stdarg.h>
#include <errno.h>
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>

#include <sys/un.h>
#include <netinet/in.h>

#include <arpa/inet.h>

#include <unistd.h>

#include <stddef.h>

#include <sys/endian.h>

#define  MAX_LOG_MESSAGE_LENGTH 1024

#define  MAX_BUFFER_SZIE 256

static int NewTcpSocket(JNIEnv *env, jobject obj);

static void BindSocketToPort(JNIEnv *env, jobject obj, int sd, unsigned short port);

static unsigned short GetSocketPort(JNIEnv *env, jobject obj, int sd);

static void ListenOnSocket(JNIEnv *env, jobject obj, int sd, int backlog);

static void
LogAddress(JNIEnv *env, jobject obj, const char *message, const struct sockaddr_in *address);

static ssize_t
SendToSocker(JNIEnv *env, jobject obj, int sd, const char *buffer, size_t bufferSize);

static ssize_t
ReceiveFromSocket(JNIEnv *env, jobject obj, int sd, char *buffer, size_t bufferSize);

static int AcceptOnSocket(JNIEnv *env, jobject obj, int sd);

static void ThrowErrnoException(JNIEnv *env, const char *className, int errnum);

static void
ConnectToAddress(JNIEnv *env, jobject obj, int sd, const char *ip, unsigned short port);

static int NewUdpSocket(JNIEnv *env, jobject obj);

static ssize_t ReceiveDatagramFromSocket(JNIEnv *env, jobject obj, int sd,
                                         struct sockaddr_in *address, char *buffer,
                                         size_t bufferSize);

static ssize_t SendDatagramToSpcket(JNIEnv *env, jobject obj, int sd,
                                    const struct sockaddr_in *address, const char *buffer,
                                    size_t bufferSize);


//日志方法id
static jmethodID methodId = NULL;

/**
 * 将消息记录传到应用程序
 * @param env
 * @param obj
 * @param format
 * @param ...
 */
static void LogMessage(JNIEnv *env, jobject obj, const char *format, ...) {

    if (NULL == methodId) {

        jclass clazz = env->GetObjectClass(obj);
        methodId = env->GetMethodID(clazz, "logMessage", "(Ljava/lang/String;)V");
        env->DeleteLocalRef(clazz);
    }
    if (NULL != methodId) {
        //日志消息
        char buffer[MAX_LOG_MESSAGE_LENGTH];

        va_list ap;
        va_start(ap, format);
        vsnprintf(buffer, MAX_LOG_MESSAGE_LENGTH, format, ap);
        va_end(ap);

        //转换成java字符串
        jstring message = env->NewStringUTF(buffer);

        if (NULL != message) {

            env->CallVoidMethod(obj, methodId, message);
            env->DeleteLocalRef(message);
        }

    }
}

/**
 * 用给定的异常类和异常消息抛出新的异常
 */
static void ThrowException(JNIEnv *env, const char *className, char *message) {

    jclass clazz = env->FindClass(className);
    if (NULL != clazz) {

        env->ThrowNew(clazz, message);
        env->DeleteLocalRef(clazz);
    }
}

/**
 * 用给定异常类和基于错误号的错误消息抛出新异常
 * @param env
 * @param className
 * @param errnum
 */
static void ThrowErrnoException(JNIEnv *env, const char *className, int errnum) {

    char buffer[MAX_LOG_MESSAGE_LENGTH];
    if (-1 == strerror_r(errnum, buffer, MAX_LOG_MESSAGE_LENGTH)) {

        strerror_r(errno, buffer, MAX_LOG_MESSAGE_LENGTH);
    }
    ThrowException(env, className, buffer);

}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_testnt_EchoServerActivity_nativeStartTcpServer(JNIEnv *env, jobject thiz,
                                                                jint port) {
    int serverSocket = NewTcpSocket(env, thiz);
    if (NULL == env->ExceptionOccurred()) {

        BindSocketToPort(env, thiz, serverSocket, port);
        if (NULL != env->ExceptionOccurred()) {

            goto exit;
        }
        if (0 == port) {

            GetSocketPort(env, thiz, serverSocket);
            if (NULL != env->ExceptionOccurred()) {

                goto exit;
            }
        }
        //监听有4个等待连接的backlog的socket
        ListenOnSocket(env, thiz, serverSocket, 4);
        if (NULL != env->ExceptionOccurred()) {
            goto exit;
        }
        //接受socket的一个客户连接
        int clientSocket = AcceptOnSocket(env, thiz, serverSocket);
        if (NULL != env->ExceptionOccurred()) {
            goto exit;
        }

        char buffer[MAX_BUFFER_SZIE];
        ssize_t recvSize;
        ssize_t sentSize;
        while (1) {

            recvSize = ReceiveFromSocket(env, thiz, clientSocket, buffer, MAX_BUFFER_SZIE);
            if ((0 == recvSize) || (NULL != env->ExceptionOccurred())) {
                break;
            }
            sentSize = SendToSocker(env, thiz, clientSocket, buffer, (size_t) recvSize);
            if ((0 == sentSize) || (NULL != env->ExceptionOccurred())) {

                break;
            }
        }
        close(clientSocket);
    }

    exit:
    if (serverSocket > 0) {

        close(serverSocket);
    }
}

/**
 * udp服务
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_example_testnt_EchoServerActivity_nativeStartUdpServer(JNIEnv *env, jobject thiz,
                                                                jint port) {

    int serverSocket = NewUdpSocket(env, thiz);
    if (NULL == env->ExceptionOccurred()) {

        BindSocketToPort(env, thiz, serverSocket, (unsigned short) port);
        if (NULL != env->ExceptionOccurred()) {

            goto exit;
        }
        if (0 == port) {

            GetSocketPort(env, thiz, serverSocket);
            if (NULL != env->ExceptionOccurred()) {
                goto exit;
            }
        }
        struct sockaddr_in address;
        memset(&address, 0, sizeof(address));

        char buffer[MAX_BUFFER_SZIE];
        ssize_t recvSize;
        ssize_t sentSize;

        //接收
        recvSize = ReceiveDatagramFromSocket(env, thiz, serverSocket, &address, buffer,
                                             MAX_BUFFER_SZIE);
        if ((0 == recvSize) || (NULL != env->ExceptionOccurred())) {

            goto exit;
        }
        sentSize = SendDatagramToSpcket(env, thiz, serverSocket, &address, buffer,
                                        (size_t) recvSize);
        if ((0 == sentSize) || (NULL != env->ExceptionOccurred())) {

            goto exit;
        }
    }
    exit:
    if (serverSocket > 0) {

        close(serverSocket);
    }
}

/**
 * 创建socket
 * @param env
 * @param obj
 * @return
 */
static int NewTcpSocket(JNIEnv *env, jobject obj) {

    LogMessage(env, obj, "创建一个新的socket...");
    int tcpSocket = socket(PF_INET, SOCK_STREAM, 0);
    if (tcpSocket == -1) {

        ThrowErrnoException(env, "java/io/IOException", errno);
    }
    return tcpSocket;
}

/**
 * 绑定到port端口
 * @param env
 * @param obj
 * @param sd
 * @param port
 */
static void BindSocketToPort(JNIEnv *env, jobject obj, int sd, unsigned short port) {

    struct sockaddr_in address;

    memset(&address, 0, sizeof(address));
    address.sin_family = PF_INET;
    //绑定到所有地址
    address.sin_addr.s_addr = htonl(INADDR_ANY);
    //将端口转换为网络字节顺序
    address.sin_port = htons(port);

    LogMessage(env, obj, "绑定到端口%hu", port);

    //绑定socket
    if (-1 == bind(sd, (struct sockaddr *) &address, sizeof(address))) {

        ThrowErrnoException(env, "java/io/IOException", errno);
    }
}

/**
 * 获取自动绑定的端口号
 * @return
 */
static unsigned short GetSocketPort(JNIEnv *env, jobject obj, int sd) {

    unsigned short port = 0;
    struct sockaddr_in address;
    socklen_t addressLength = sizeof(address);

    if (-1 == getsockname(sd, (struct sockaddr *) &address, &addressLength)) {

        ThrowErrnoException(env, "java/io/IOException", errno);
    } else {

        //建端口转换为主机字节顺序
        port = ntohs(address.sin_port);
        LogMessage(env, obj, "绑定的端口是%hu", port);
    }
    return port;
}

/**
 * 监听连接
 * @param env
 * @param obj
 * @param sd
 * @param backlog
 */
static void ListenOnSocket(JNIEnv *env, jobject obj, int sd, int backlog) {

    LogMessage(env, obj, "数量%d", backlog);
    if (-1 == listen(sd, backlog)) {

        ThrowErrnoException(env, "java/io/Exception", errno);
    }
}

/**
 * 记录给定地址的ip地址和端口号
 * @param env
 * @param obj
 * @param message
 * @param address
 */
static void
LogAddress(JNIEnv *env, jobject obj, const char *message, const struct sockaddr_in *address) {

    char ip[INET_ADDRSTRLEN];
    //将数值格式转化为点分十进制的ip地址格式
    if (NULL == inet_ntop(PF_INET, &(address->sin_addr), ip, INET_ADDRSTRLEN)) {

        ThrowErrnoException(env, "java/io/IOException", errno);
    } else {

        unsigned short port = ntohs(address->sin_port);
        LogMessage(env, obj, "%s %s:%hu.", message, ip, port);
    }
}

/**
 * 在给定的socket上阻塞和等待进来的客户连接
 * @param env
 * @param obj
 * @param sd
 * @return
 */
static int AcceptOnSocket(JNIEnv *env, jobject obj, int sd) {

    struct sockaddr_in address;
    socklen_t addressLength = sizeof(address);
    //阻塞和等待进来的客户连接
    LogMessage(env, obj, "等待连接");

    int clientSocket = accept(sd, (struct sockaddr *) &address, &addressLength);
    if (-1 == clientSocket) {

        ThrowErrnoException(env, "java/io/IOException", errno);
    } else {

        LogAddress(env, obj, "来自客户端的连接", &address);
    }
    return clientSocket;
}


/**
 * 读入数据
 * @param env
 * @param obj
 * @param sd
 * @param buffer
 * @param bufferSize
 * @return
 */
static ssize_t
ReceiveFromSocket(JNIEnv *env, jobject obj, int sd, char *buffer, size_t bufferSize) {

    LogMessage(env, obj, "接收数据");
    ssize_t recvSize = recv(sd, buffer, bufferSize - 1, 0);


    if (-1 == recvSize) {
        ThrowErrnoException(env, "java/io/IOException", errno);
    } else {

        buffer[recvSize] = NULL;
        if (recvSize > 0) {

            LogMessage(env, obj, "接收到%d字节：%s", recvSize, buffer);
        } else {

            LogMessage(env, obj, "断开连接");
        }
    }
    return recvSize;
}

/**
 * 将数据发送到socket
 * @param env
 * @param obj
 * @param sd
 * @param buffer
 * @param bufferSize
 * @return
 */
static ssize_t
SendToSocker(JNIEnv *env, jobject obj, int sd, const char *buffer, size_t bufferSize) {

    LogMessage(env, obj, "发送数据");
    ssize_t sendSize = send(sd, buffer, bufferSize, 0);

    if (-1 == sendSize) {
        ThrowErrnoException(env, "java/io/IOException", errno);
    } else {

        if (sendSize > 0) {

            LogMessage(env, obj, "发送%d字节：%s", sendSize, buffer);
        } else {
            LogMessage(env, obj, "连接失败");
        }
    }

    return sendSize;
}


/**
 * 客户端发送信息
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_example_testnt_EchoClientActivity_nativeStartTcpClient(JNIEnv *env, jobject thiz,
                                                                jstring ip, jint port,
                                                                jstring message) {

    int clientSocket = NewTcpSocket(env, thiz);
    if (NULL == env->ExceptionOccurred()) {

        const char *ipAddress = env->GetStringUTFChars(ip, NULL);
        if (NULL == ipAddress) {

            goto exit;
        }

        ConnectToAddress(env, thiz, clientSocket, ipAddress, (unsigned short) port);
        //释放ip地址
        env->ReleaseStringUTFChars(ip, ipAddress);

        if (NULL != env->ExceptionOccurred()) {
            goto exit;
        }

        const char *messageText = env->GetStringUTFChars(message, NULL);
        if (NULL == messageText) {
            goto exit;
        }
        //过去消息大小
        jsize messageSize = env->GetStringUTFLength(message);
        //发送消息给socket
        SendToSocker(env, thiz, clientSocket, messageText, (size_t) messageSize);
        //释放
        env->ReleaseStringUTFChars(message, messageText);
        if (NULL != env->ExceptionOccurred()) {

            goto exit;
        }
        char buffer[MAX_BUFFER_SZIE];

        ReceiveFromSocket(env, thiz, clientSocket, buffer, MAX_BUFFER_SZIE);

    }
    exit:
    if (clientSocket > -1) {

        close(clientSocket);
    }

}

/**
 * 客户端连接指定ip和port
 * @param env
 * @param obj
 * @param sd
 * @param ip
 * @param port
 */
static void
ConnectToAddress(JNIEnv *env, jobject obj, int sd, const char *ip, unsigned short port) {

    LogMessage(env, obj, "连接%s:%hu", ip, port);
    struct sockaddr_in address;

    memset(&address, 0, sizeof(address));

    address.sin_family = PF_INET;
    if (0 == inet_aton(ip, &(address.sin_addr))) {

        ThrowErrnoException(env, "java/io/IOException", errno);
    } else {

        address.sin_port = htons(port);
        if (-1 == connect(sd, (const sockaddr *) &address, sizeof(address))) {

            ThrowErrnoException(env, "java/io/IOException", errno);
        } else {
            LogMessage(env, obj, "客户端已连接");
        }
    }

}

/**
 * 创建udp
 * @param env
 * @param obj
 * @return
 */
static int NewUdpSocket(JNIEnv *env, jobject obj) {

    LogMessage(env, obj, "创建udp socket的连接");
    int udpSocket = socket(PF_INET, SOCK_DGRAM, 0);
    if (-1 == udpSocket) {

        ThrowErrnoException(env, "java/io/IOException", errno);
    }
    return udpSocket;
}

/**
 * 接收数据报
 * @param env
 * @param obj
 * @param sd
 * @param address
 * @param buffer
 * @param bufferSize
 * @return
 */
static ssize_t ReceiveDatagramFromSocket(JNIEnv *env, jobject obj, int sd,
                                         struct sockaddr_in *address, char *buffer,
                                         size_t bufferSize) {

    socklen_t addressLength = sizeof(struct sockaddr_in);
    //从socket中接受数据包
    LogMessage(env, obj, "从socket接受数据");
    ssize_t recvSize = recvfrom(sd, buffer, bufferSize, 0, (struct sockaddr *) address,
                                &addressLength);

    if (-1 == recvSize) {

        ThrowErrnoException(env, "java/io/IOException", errno);
    } else {

        LogAddress(env, obj, "接收到", address);
        //以NULL种植缓冲区使其为一个字符串
        buffer[recvSize] = NULL;

        if (recvSize > 0) {

            LogMessage(env, obj, "接收到%d字节：%s", recvSize, buffer);
        }
    }
    return recvSize;
}


/**
 * 发送数据报到指定地址
 * @param env
 * @param obj
 * @param sd
 * @param address
 * @param buffer
 * @param bufferSize
 * @return
 */
static ssize_t SendDatagramToSpcket(JNIEnv *env, jobject obj, int sd,
                                    const struct sockaddr_in *address, const char *buffer,
                                    size_t bufferSize) {


    LogAddress(env, obj, "Sending to", address);
    ssize_t sentSize = sendto(sd, buffer, bufferSize, 0, (const sockaddr *) address,
                              sizeof(struct sockaddr));//改
    if (-1 == sentSize) {

        ThrowErrnoException(env, "java/io/IOException", errno);
    } else if (sentSize > 0) {

        LogMessage(env, obj, "sent %d 字节：%s", sentSize, buffer);
    }
    return sentSize;
}


/**
 * udp客户端
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_example_testnt_EchoClientActivity_nativeStartUdpClient(JNIEnv *env, jobject thiz,
                                                                jstring ip, jint port,
                                                                jstring message) {
    int clientSocket = NewUdpSocket(env, thiz);
    if (NULL == env->ExceptionOccurred()) {

        struct sockaddr_in address;
        memset(&address, 0, sizeof(address));
        address.sin_family = PF_INET;

        const char *ipAddress = env->GetStringUTFChars(ip, NULL);
        if (NULL == ipAddress) {
            goto exit;
        }
        //将IP地址字符串转换为网络地址
        int result = inet_aton(ipAddress, &(address.sin_addr));
        env->ReleaseStringUTFChars(ip, ipAddress);

        if (0 == result) {

            ThrowErrnoException(env, "java/io/IOException", errno);
            goto exit;
        }
        //转换为网络字节顺序
        address.sin_port = htons(port);

        const char *messageText = env->GetStringUTFChars(message, NULL);
        if (NULL == messageText) {
            goto exit;
        }

        jsize messageSize = env->GetStringUTFLength(message);

        //发送
        SendDatagramToSpcket(env, thiz, clientSocket, &address, messageText, (size_t) messageSize);
        env->ReleaseStringUTFChars(message, messageText);

        //失败
        if (NULL != env->ExceptionOccurred()) {
            goto exit;
        }

        char buffer[MAX_BUFFER_SZIE];
        memset(&address, 0, sizeof(address));

        ReceiveDatagramFromSocket(env, thiz, clientSocket, &address, buffer, MAX_BUFFER_SZIE);

    }
    exit:
    if (clientSocket > 0) {

        close(clientSocket);
    }
}

/**
 * 创建原生本地socket
 * @param env
 * @param obj
 * @return
 */
static int NewLocalSocket(JNIEnv *env, jobject obj) {

    LogMessage(env, obj, "创建本地socket");
    int localSocket = socket(PF_LOCAL, SOCK_STREAM, 0);
    if (-1 == localSocket) {

        ThrowErrnoException(env, "java/io/IOException", errno);
    }
    return localSocket;
}

/**
 * 将本地socket与某一名称绑定
 *
 */
static void BindLocalSocketToName(JNIEnv *env, jobject obj, int sd, const char *name) {

    struct sockaddr_un address;
    //长度
    const size_t nameLength = strlen(name);
    size_t pathLength = nameLength;

    //如果名字不以“/”开头，则在Abstract namespace
    bool abstractNamespace = ('/' != name[0]);

    //抽象命名空间要求目录的第一个字节是0字节，更新路径长度包括0字节
    if (abstractNamespace) {

        pathLength++;
    }

    if (pathLength > sizeof(address.sun_path)) {

        ThrowException(env, "java/io/IOException", "名字太长");
    } else {

        memset(&address, 0, sizeof(address));
        address.sun_family = PF_LOCAL;

        char *sunPath = address.sun_path;

        //第一字节必须是0以使用抽象命名空间
        if (abstractNamespace) {

            *sunPath++ = NULL;
        }

        strcpy(sunPath, name);
        //地址长度，offsetof会生成一个类型为 size_t 的整型常量，它是一个结构成员相对于结构开头的字节偏移量。
        socklen_t addressLength = (offsetof(struct sockaddr_un, sun_path)) + pathLength;

        //如果socket名已经绑定，取消连接
        unlink(address.sun_path);

        LogMessage(env, obj, "绑定到本地名称%s%s", (abstractNamespace) ? "(null)" : "", name);
        if (-1 == bind(sd, (struct sockaddr *) &address, addressLength)) {

            ThrowErrnoException(env, "java/io/IOException", errno);
        }
    }
}

/**
 * 阻塞并等待socket上客户端的连接
 * @param env
 * @param obj
 * @param sd
 * @return
 */
static int AcceptOnLocalSocket(JNIEnv *env, jobject obj, int sd) {

    LogMessage(env, obj, "等待客户端连接");
    int clientSocket = accept(sd, NULL, NULL);
    if (-1 == clientSocket) {

        ThrowErrnoException(env, "java/io/IOException", errno);
    }
    return clientSocket;
}

/**
 * 启动本地unix socket 服务器
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_example_testnt_LocalEchoActivity_nativeStartLocalServer(JNIEnv *env, jobject thiz,
                                                                 jstring name) {

    int serverSocket = NewLocalSocket(env, thiz);
    if (NULL == env->ExceptionOccurred()) {

        const char *nameText = env->GetStringUTFChars(name, NULL);
        if (NULL == nameText) {

            goto exit;
        }
        //绑定
        BindLocalSocketToName(env, thiz, serverSocket, nameText);
        env->ReleaseStringUTFChars(name, nameText);

        if (NULL != env->ExceptionOccurred()) {
            goto exit;
        }

        ListenOnSocket(env, thiz, serverSocket, 4);

        if (NULL != env->ExceptionOccurred()) {

            goto exit;
        }

        int clientSocket = AcceptOnLocalSocket(env, thiz, serverSocket);
        if (NULL != env->ExceptionOccurred()) {
            goto exit;
        }
        char buffer[MAX_BUFFER_SZIE];
        ssize_t recvSize;
        ssize_t sentSize;
        while (1) {

            recvSize = ReceiveFromSocket(env, thiz, clientSocket, buffer, MAX_BUFFER_SZIE);
            if ((0 == recvSize) || (NULL != env->ExceptionOccurred())) {
                break;
            }
            sentSize = SendToSocker(env, thiz, clientSocket, buffer, (size_t) recvSize);
            if ((0 == sentSize) || (NULL != env->ExceptionOccurred())) {
                break;
            }
        }
        close(clientSocket);
    }
    exit:
    if (serverSocket > 0) {

        close(serverSocket);
    }
}